namespace Smarts.Internal.Test.Core
{
    using System;
    using NUnit.Framework;
    using Smarts.Core;
    using TestDataDataSetTableAdapters;

    /// <summary>
    /// Tests for the Formatter class, focussing on number formatting
    /// </summary>
    [TestFixture]
    public class NumberFormatterTests
    {
        /// <summary>
        /// Table containing the numbers used as test data
        /// for these tests
        /// </summary>
        private TestDataDataSet.NumberWordsDataTable _numbersTable;

        /// <summary>
        /// Set up the number table for the tests
        /// </summary>
        [SetUp]
        public void SetupData()
        {
            _numbersTable = new TestDataDataSet.NumberWordsDataTable();
            new NumberWordsTableAdapter().Fill(_numbersTable);
        }

        /// <summary>
        /// Verify that the formatter constants are as expected
        /// </summary>
        [Test]
        public void FormatterConstants()
        {
            Assert.That(Formatter.OneBillion, Is.EqualTo(1000000000));
            Assert.That(Formatter.OneHundred, Is.EqualTo(100));
            Assert.That(Formatter.OneMillion, Is.EqualTo(1000000));
            Assert.That(Formatter.OneThousand, Is.EqualTo(1000));
        }

        /// <summary>
        /// Test that the formatter can turn numbers into words
        /// </summary>
        [Test]
        public void FormatNumbers()
        {
            foreach (var number in _numbersTable)
            {
                Assert.That(Formatter.NumberWords((int)number.NumberForm), Is.EqualTo(number.WordForm), "Word form of {0}", number.NumberForm);
            }
        }

        /// <summary>
        /// Test that the formatter can conditionally not format numbers
        /// when they cross a threshold
        /// </summary>
        [Test]
        public void FormatNumbersWithLimit()
        {
            foreach (var number in _numbersTable)
            {
                if (number.NumberForm > Formatter.OneThousand)
                {
                    Assert.That(Formatter.NumberWords((int)number.NumberForm, Formatter.OneThousand), Is.EqualTo(number.NumberForm.ToString()));
                }
                else
                {
                    Assert.That(Formatter.NumberWords((int)number.NumberForm, Formatter.OneThousand), Is.EqualTo(number.WordForm), "Word form of " + number.NumberForm);
                }
            }
        }

        /// <summary>
        /// Test that fractional doubles are able to be translated to
        /// word form
        /// </summary>
        [Test]
        public void FormatFractionalDouble()
        {
            Assert.That(Formatter.NumberWords(0.5), Is.EqualTo("zero point five"));
            Assert.That(Formatter.NumberWords(1.0), Is.EqualTo("one"));
            Assert.That(Formatter.NumberWords(123.592), Is.EqualTo("one hundred and twenty-three point five nine two"));
            Assert.That(Formatter.NumberWords(Math.PI), Is.EqualTo("three point one four one five nine two six five three five eight nine seven nine"));
        }

        /// <summary>
        /// Test that strings with no numbers will be unaffected
        /// by a number formatter
        /// </summary>
        [Test]
        public void FormatStringsWithoutNumbers()
        {
            Assert.That(Formatter.NumberWords("This has no numbers. none"), Is.EqualTo("This has no numbers. none"));
        }

        /// <summary>
        /// Test that strings with one number are changed by
        /// the number formatter
        /// </summary>
        [Test]
        public void FormatStringWithJustOneNumber()
        {
            foreach (var number in _numbersTable)
            {
                Assert.That(Formatter.NumberWords(number.NumberForm.ToString()), Is.EqualTo(number.WordForm));
            }
        }

        /// <summary>
        /// Test that a string with one number and surrounding text is
        /// able to be formatted by the number formatter
        /// </summary>
        [Test]
        public void FormatStringWithJustOneNumberAndSurroundingText()
        {
            var containerString = "My favourite number is {0}. It is the best!";
            foreach (var number in _numbersTable)
            {
                Assert.That(
                    string.Format(containerString, Formatter.NumberWords(number.NumberForm.ToString())),
                    Is.EqualTo(string.Format(containerString, number.WordForm)));
            }
        }

        /// <summary>
        /// Test that the number formatter is able to format multiple numbers
        /// in a string
        /// </summary>
        [Test]
        public void FormatStringWithMultipleNumbersAndSurroundingText()
        {
            var originalString = "I bought 2 apples at 50 percent off";
            var withWords = "I bought two apples at fifty percent off";
            Assert.That(Formatter.NumberWords(originalString), Is.EqualTo(withWords));
        }

        /// <summary>
        /// Test that the number formatter is able to enforce an upper limit
        /// on number translation when numbers exist in a string
        /// </summary>
        [Test]
        public void FormatStringWithMultipleNumbersAndSurroundingTextToLimit()
        {
            var originalString = "I bought 2 apples at 50 percent off";
            var withWords = "I bought two apples at fifty percent off";
            var withWordsLimit = "I bought two apples at 50 percent off";
            Assert.That(Formatter.NumberWords(originalString, 49), Is.EqualTo(withWordsLimit));
            Assert.That(Formatter.NumberWords(originalString, 50), Is.EqualTo(withWords));
        }

        /// <summary>
        /// Test that the number formatter can handle strings
        /// with fractional numbers inside them
        /// </summary>
        [Test]
        public void FormatStringWithFractions()
        {
            Assert.That(Formatter.NumberWords("There is 0.5 level of risk"), Is.EqualTo("There is zero point five level of risk"));
            Assert.That(Formatter.NumberWords("I am 1.0 cool dude"), Is.EqualTo("I am one cool dude"));
            Assert.That(Formatter.NumberWords("The number 123.592 seems cool"), Is.EqualTo("The number one hundred and twenty-three point five nine two seems cool"));
            Assert.That(Formatter.NumberWords("Pi is approximately " + Math.PI + ". Pi is not exactly 3."), Is.EqualTo("Pi is approximately three point one four one five nine two six five three five eight nine seven nine. Pi is not exactly three."));
        }
    }
}